package com.sanbi.shield.client.core.aop;

import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.alibaba.com.caucho.hessian.io.SerializerFactory;
import com.sanbi.shield.client.core.interfaces.ClientIdempotentManager;
import com.sanbi.shield.client.core.utils.single.ShieldExpressionEvaluator;
import com.sanbi.shield.server.facade.annotations.ClientIdempotent;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;

import java.io.ByteArrayOutputStream;
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Base64;

/**
 * Client幂等切面
 *
 * @author XiaoQingqing
 * @since 2024/08/22
 */
@Slf4j
@Aspect
public class ClientIdempotentAspect {
    /**
     * Shield表达式计算器
     */
    private final ShieldExpressionEvaluator<String> evaluator = new ShieldExpressionEvaluator<>();
    /**
     * Client幂等管理器
     */
    private final ClientIdempotentManager manager;

    /**
     * 构造器
     *
     * @param manager Client幂等管理器
     */
    public ClientIdempotentAspect(ClientIdempotentManager manager){
        this.manager = manager;
    }

    /**
     * Client幂等切点
     */
    @SuppressWarnings("unused")
    @Pointcut("@annotation(com.sanbi.shield.server.facade.annotations.ClientIdempotent)")
    public void clientIdempotent() {}

    /**
     * Client幂等环绕处理方法
     *
     * @param joinPoint 切点
     * @return 执行结果
     * @throws Throwable 异常
     */
    @SuppressWarnings("unused")
    @Around("clientIdempotent()")
    public Object clientIdempotentAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        if(!(signature instanceof MethodSignature methodSignature)){
            return joinPoint.proceed();
        }
        Method method = methodSignature.getMethod();
        ClientIdempotent clientIdempotent = method.getAnnotation(ClientIdempotent.class);
        if(null == clientIdempotent){
            return joinPoint.proceed();
        }
        String key;
        if(null != clientIdempotent.key() && clientIdempotent.key().contains("#")){
            EvaluationContext evaluationContext = evaluator.createContext(joinPoint.getTarget(),
                    joinPoint.getTarget().getClass(), methodSignature.getMethod(), joinPoint.getArgs());
            AnnotatedElementKey methodKey = new AnnotatedElementKey(methodSignature.getMethod(), joinPoint.getTarget().getClass());
            key = evaluator.evaluateExpression(clientIdempotent.key(), methodKey, evaluationContext, String.class);
        } else {
            key = clientIdempotent.key();
        }
        if(manager.exist(clientIdempotent.methodKey(), key, clientIdempotent.scope())){
            return manager.handleExisted(clientIdempotent.methodKey(), key, clientIdempotent.scope());
        }
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setArgs(joinPoint.getArgs());
        byte[] requestInfoBytes;
        try(ByteArrayOutputStream bos = new ByteArrayOutputStream()
        ){
            SerializerFactory factory = SerializerFactory.createDefault();
            factory.setAllowNonSerializable(true);
            Hessian2Output hessian2Output = new Hessian2Output(bos);
            hessian2Output.setSerializerFactory(factory);
            hessian2Output.writeObject(requestInfo);
            hessian2Output.flush();
            requestInfoBytes = bos.toByteArray();
        }
        String requestInfoStr = Base64.getEncoder().encodeToString(requestInfoBytes);
        manager.save(clientIdempotent.methodKey(), key, clientIdempotent.scope(), clientIdempotent.retryTimes(), requestInfoStr);
        if(ClientIdempotent.Mode.ASYNC.equals(clientIdempotent.mode())){
            manager.getExecutor().execute(() -> {
                try {
                    joinPoint.proceed();
                    manager.success(clientIdempotent.methodKey(), key, clientIdempotent.scope());
                } catch (Throwable e) {
                    log.error("异步执行幂等方法异常", e);
                    manager.fail(clientIdempotent.methodKey(), key, clientIdempotent.scope(),0, e);
                }
            });
            return null;
        }
        Object result;
        try {
            result = joinPoint.proceed();
            manager.success(clientIdempotent.methodKey(), key, clientIdempotent.scope());
        } catch (Throwable e) {
            log.error("同步执行幂等方法异常", e);
            manager.fail(clientIdempotent.methodKey(), key, clientIdempotent.scope(),0, e);
            if(ClientIdempotent.Mode.SYNC_THROWABLE.equals(clientIdempotent.mode())){
                throw e;
            }
            return null;
        }
        return result;
    }

    /**
     * 请求信息
     */
    @Data
    public static class RequestInfo implements Serializable {
        @Serial
        private static final long serialVersionUID = 1L;
        /**
         * 请求参数
         */
        private Object[] args;
    }
}
